/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.handler.component;

import static org.apache.solr.common.params.CommonParams.FQ;
import java.io.IOException;
import java.net.URL;
import java.util.*;

import org.apache.lucene.search.Query;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.util.SolrPluginUtils;

/**
 * Adds debugging information to a request.
 * 
 * @version $Id: DebugComponent.java 1065312 2011-01-30 16:08:25Z rmuir $
 * @since solr 1.3
 */
public class DebugComponent extends SearchComponent {
	public static final String COMPONENT_NAME = "debug";

	@Override
	public void prepare(ResponseBuilder rb) throws IOException {

	}

	@SuppressWarnings("unchecked")
	@Override
	public void process(ResponseBuilder rb) throws IOException {
		if (rb.isDebug()) {
			NamedList stdinfo = SolrPluginUtils
					.doStandardDebug(rb.req, rb.getQueryString(),
							rb.getQuery(), rb.getResults().docList);

			NamedList info = rb.getDebugInfo();
			if (info == null) {
				rb.setDebugInfo(stdinfo);
				info = stdinfo;
			} else {
				info.addAll(stdinfo);
			}

			if (rb.getQparser() != null) {
				rb.getQparser().addDebugInfo(rb.getDebugInfo());
			}

			if (null != rb.getDebugInfo()) {
				if (null != rb.getFilters()) {
					info.add("filter_queries", rb.req.getParams().getParams(FQ));
					List<String> fqs = new ArrayList<String>(rb.getFilters()
							.size());
					for (Query fq : rb.getFilters()) {
						fqs.add(QueryParsing.toString(fq, rb.req.getSchema()));
					}
					info.add("parsed_filter_queries", fqs);
				}

				// Add this directly here?
				rb.rsp.add("debug", rb.getDebugInfo());
			}
		}
	}

	@Override
	public void modifyRequest(ResponseBuilder rb, SearchComponent who,
			ShardRequest sreq) {
		if (!rb.isDebug())
			return;

		// Turn on debug to get explain only when retrieving fields
		if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) != 0) {
			sreq.purpose |= ShardRequest.PURPOSE_GET_DEBUG;
			sreq.params.set("debugQuery", "true");
		} else {
			sreq.params.set("debugQuery", "false");
		}
	}

	@Override
	public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
	}

	private Set<String> excludeSet = new HashSet<String>(
			Arrays.asList("explain"));

	@Override
	public void finishStage(ResponseBuilder rb) {
		if (rb.isDebug() && rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {
			NamedList info = null;
			NamedList explain = new SimpleOrderedMap();

			Map.Entry<String, Object>[] arr = new NamedList.NamedListEntry[rb.resultIds
					.size()];

			for (ShardRequest sreq : rb.finished) {
				if ((sreq.purpose & ShardRequest.PURPOSE_GET_DEBUG) == 0)
					continue;
				for (ShardResponse srsp : sreq.responses) {
					NamedList sdebug = (NamedList) srsp.getSolrResponse()
							.getResponse().get("debug");
					info = (NamedList) merge(sdebug, info, excludeSet);

					NamedList sexplain = (NamedList) sdebug.get("explain");

					for (int i = 0; i < sexplain.size(); i++) {
						String id = sexplain.getName(i);
						// TODO: lookup won't work for non-string ids... String
						// vs Float
						ShardDoc sdoc = rb.resultIds.get(id);
						int idx = sdoc.positionInResponse;
						arr[idx] = new NamedList.NamedListEntry<Object>(id,
								sexplain.getVal(i));
					}
				}
			}

			explain = HighlightComponent.removeNulls(new SimpleOrderedMap(arr));

			if (info == null) {
				info = new SimpleOrderedMap();
			}
			int idx = info.indexOf("explain", 0);
			if (idx >= 0) {
				info.setVal(idx, explain);
			} else {
				info.add("explain", explain);
			}

			rb.setDebugInfo(info);
			rb.rsp.add("debug", rb.getDebugInfo());
		}
	}

	Object merge(Object source, Object dest, Set<String> exclude) {
		if (source == null)
			return dest;
		if (dest == null) {
			if (source instanceof NamedList) {
				dest = source instanceof SimpleOrderedMap ? new SimpleOrderedMap()
						: new NamedList();
			} else {
				return source;
			}
		} else {

			if (dest instanceof Collection) {
				if (source instanceof Collection) {
					((Collection) dest).addAll((Collection) source);
				} else {
					((Collection) dest).add(source);
				}
				return dest;
			} else if (source instanceof Number) {
				if (dest instanceof Number) {
					if (source instanceof Double || dest instanceof Double) {
						return ((Number) source).doubleValue()
								+ ((Number) dest).doubleValue();
					}
					return ((Number) source).longValue()
							+ ((Number) dest).longValue();
				}
				// fall through
			} else if (source instanceof String) {
				if (source.equals(dest)) {
					return dest;
				}
				// fall through
			}
		}

		if (source instanceof NamedList && dest instanceof NamedList) {
			NamedList tmp = new NamedList();
			NamedList sl = (NamedList) source;
			NamedList dl = (NamedList) dest;
			for (int i = 0; i < sl.size(); i++) {
				String skey = sl.getName(i);
				if (exclude != null && exclude.contains(skey))
					continue;
				Object sval = sl.getVal(i);
				int didx = -1;

				// optimize case where elements are in same position
				if (i < dl.size()) {
					String dkey = dl.getName(i);
					if (skey == dkey || (skey != null && skey.equals(dkey))) {
						didx = i;
					}
				}

				if (didx == -1) {
					didx = dl.indexOf(skey, 0);
				}

				if (didx == -1) {
					tmp.add(skey, merge(sval, null, null));
				} else {
					dl.setVal(didx, merge(sval, dl.getVal(didx), null));
				}
			}
			dl.addAll(tmp);
			return dl;
		}

		// merge unlike elements in a list
		List t = new ArrayList();
		t.add(dest);
		t.add(source);
		return t;
	}

	// ///////////////////////////////////////////
	// / SolrInfoMBean
	// //////////////////////////////////////////

	@Override
	public String getDescription() {
		return "Debug Information";
	}

	@Override
	public String getVersion() {
		return "$Revision: 1065312 $";
	}

	@Override
	public String getSourceId() {
		return "$Id: DebugComponent.java 1065312 2011-01-30 16:08:25Z rmuir $";
	}

	@Override
	public String getSource() {
		return "$URL: https://svn.apache.org/repos/asf/lucene/dev/branches/branch_3x/solr/core/src/java/org/apache/solr/handler/component/DebugComponent.java $";
	}

	@Override
	public URL[] getDocs() {
		return null;
	}
}
